#!/usr/bin/env python


##########################################################
##########################################################
##							##
##               Flexible Registry Parser		##
##               Author: Paul-Henri Huckel		##
##               Contact: @FrOzPolow			##
##							##
##########################################################
##########################################################

#TODO: Manage user list
#TODO: MRUList formating

import hivex
import sys
import getopt
import re
import os
import codecs
import binascii
import datetime
from xml.etree import ElementTree
from optparse import OptionParser, Option, OptionValueError

VERBOSITY = 0

###
### Return path of the hive file
###
### 
def get_hive_location(category, item, OS):
	print item
	try:
		f = open(conf_file)
		tree = ElementTree.parse(f)
	except:
		print "		Something is wrong with the conf file path given" 
		return
        for node in tree.findall('./' + category + '/' + item + '/key'):
                key = node.attrib.get('OS')
                if key == OS:
                        string = node.attrib.get('Hive')
			try:
				f2 = open(hive_dir +"/" + string)
			except:
				print "		Something is wrong with the hive file, please check the conf file at line", OS, category, item
				return
                        return hive_dir +"/" + string
	return

###
### print & Format output according to the data type
### val:                list - val[0]=type, val[1]=value
### return:             string - value in val[1]
###
def print_fct ( val ):
	if val:
                # string value
                if val[0] == 1:
                        return val[1]
                # expandable string value
                if val[0] == 2:
                        return val[1]
                # Binary data
                if val[0] == 3:
#			return "The following is a REG_BINARY value, consider decoding the Hex value:  "+''.join(val[1]).encode('hex')	
			return ''.join(val[1]).encode('hex')
                # Little Indian 32bits unsigned integer
                if val[0] == 4:
                        return ''.join(val[1]).encode('hex')
                # Big Indian 32bits unsigned integer
                if val[0] == 5:
                        return ''.join(reversed(val[1])).encode('hex')
                # Long String
                if val[0] == 7:
                        return val[1]
        else:
                return " This key is not defined within the hive file"
        return "VALUE TYPE NOT SUPPORTED"


###
### Looks in the XML file and return a list of each subkey leading to the node requested
### category:           String
### item:               string
### OS:                 string 
### return:             list
###
def get_key_list (category, item, OS, username = ""):
        i = 0
        string = ""
        f = open(conf_file)
        tree = ElementTree.parse(f)
        for node in tree.findall('./' + category + '/' + item + '/key'):
                key = node.attrib.get('OS')
                if key == OS:
                        string = node.attrib.get('path')
                        break
        key_path = string.split('\\', -1)
        for key in key_path:
                if key == "CurrentControlSet":
                        currentcontrolset = get_currentcontrolset()
                        key_path[i] = "ControlSet00" + currentcontrolset
                i = i + 1
        return key_path

###
### Builds the list of values requested in the conf file
### category:           String
### item:               string
### OS:                 string 
### return:             list
###
def get_values_list ( category, item, OS ):
        values_list = []
        string = ""
        f = open(conf_file)
        tree = ElementTree.parse(f)
        for node in tree.findall('./' + category + '/' + item + '/key'):
                key = node.attrib.get('OS')
                if key == OS:
                        string = node.attrib.get('values')
                        break
        if string:
		values_list = string.split(',', -1)
        return values_list


###
### Look which controlset is the one currently in use
### return:             string - controlset
###
def get_currentcontrolset ():
	global hive_dir
        try:
                h = hivex.Hivex(hive_dir+"/system")
                hive = "/system"
        except:
                try:
                        h = hivex.Hivex(hive_dir+"/SYSTEM")
                        hive="/SYSTEM"
                except:
                        print "Could not find the current controlset... Did you provide the System hive?"
                        return

	system_hive = hive_dir+hive 
        h = hivex.Hivex(system_hive)
        key = h.root()
        key = h.node_get_child(key, "Select")
        val = h.node_get_value(key, "Current")
        val = h.value_value(val)
        return str(int(''.join(reversed(val[1])).encode('hex'), 16))

###
### Return modified time of a key
###
def get_modified_time (hive, path):
	h = hivex.Hivex(hive)
	key = get_node(hive, path)
	time = h .node_timestamp(key)
	time = int((time / 10000000) - 11644473600)
	return datetime.datetime.fromtimestamp(time).strftime('%Y-%m-%d %H:%M:%S')
	

###
### Print single value found of a single key found in path
### adds the last written time & path depending on the verbosity set by the user 
###
def get_value (hive, path, value_list, timestamp):
	global VERBOSITY

	h = hivex.Hivex(hive)
	key = get_node(hive, path)
	i = 0
	write_path = ""
	
	if not key:
		print "Key not defined in the Hive file Or no values found"
	if value_list and value_list[0] == "*":
		value_list = h.node_values(key)
		while i < len(value_list):
			value_list[i] = h.value_key(value_list[i])
			i = i + 1
        if (timestamp == 1) or (int(VERBOSITY) >= 2):
		print "		Last Written Time: " + get_modified_time(hive, path)
	if (int(VERBOSITY) == 1) or (int(VERBOSITY) == 3):
		write_path = hive + "/" + '/'.join(path)
	for value in value_list:
		try:
			val = h.node_get_value(key, value)
			val = h.value_value(val)
			print "			** ", value, ": ", print_fct(val), "	", write_path
		except:
			print "			** ", value,": not defined in the Hive file OR no values found"
	if value_list:
		print "			-- No further value listed/requested"

###
### Return list of subkey of a given node (called for type=list)
###
def get_list_child (hive, path):
	result = []
	h = hivex.Hivex(hive)
	key = get_node(hive, path)
	try:
		for element in h.node_children(key):
			result.append(h.node_name(element))
	except:
		print "		That path doesn't exist in the provided Hive file"
	return result

###
### Drill down to a specific node and return it
###
def get_node (hive, path):
	h = hivex.Hivex(hive)
	key = h.root()
	i = 0
	try:
		for iter in path:
			key = h.node_get_child(key, iter)
	except:
		print "		That path doesn't exist in the provided Hive file"
		return None
	return key

###
### Boolean function indicating if it is necessary to explore children of the current node 
###
def recursive_search (category, item, OS):
        f = open(conf_file)
        tree = ElementTree.parse(f)
        for node in tree.findall('./' + category + '/' + item + '/key'):
                key = node.attrib.get('OS')
                if key == OS:
                        string = node.attrib.get('visit_children')
                        break
        if string:
                return int(string)
        return 0

###
### Boolean function indicating if it is necessary to print the last written time of the node
###
def timestamp_on(category, item, OS):
	f = open(conf_file)
	tree = ElementTree.parse(f)
	for node in tree.findall('./' + category + '/' + item + '/key'):
		key = node.attrib.get('OS')
		if key == OS:
			string = node.attrib.get('timestamp')
			break
	if string:
		return int(string)
	return 0

###
### goes through all children from a given level
###
def recursive_children(level, hive, path, value_list, timestamp):
	h = hivex.Hivex(hive)
	key = get_node(hive, path)

        if key is None:
		print "         That item does not exist OR the conf doesn't define it for that OS"
		return  
	if level == 0:
    		print "         	**", h.node_name(key) 
		if len(value_list) is not 0:
			get_value(hive, path, value_list, timestamp)
			print
			return 
	if level == 1:
		print "         **", h.node_name(key)
       	for element in h.node_children(key):
		tmp = list(path)
		tmp.append(h.node_name(element))
              	recursive_children(level - 1, hive , tmp, value_list, timestamp)

###
### Seek & print the information
###
def print_information (category, item, OS):
	hive = get_hive_location(category, item, OS)
        if not hive:
		print "		That item does not exist OR the conf doesn't define it for that OS"
		print
                return

	values_list = get_values_list (category, item, OS)
	path = get_key_list(category, item, OS)	
	recursive = recursive_search(category, item, OS)
	timestamp = timestamp_on(category, item, OS)

        ## Getting and Printing information from each subcategory
	if recursive:
		recursive_children(recursive, hive, path, values_list, timestamp)
	else:
		get_value(hive, path, values_list, timestamp)
	print

###
### If the OS has not been given in the CL, it checks in software hive for it
### 
def autodetect_os(hive_dir):
	try:
		h = hivex.Hivex(hive_dir+"/software")
		hive = "/software"
	except:
		try: 
			h = hivex.Hivex(hive_dir+"/SOFTWARE")
			hive="/SOFTWARE"
		except:
			print "No OS specified and unable to automatically find it... Please check the Hive provided"
			return
	key = get_node(hive_dir+hive, ["Microsoft", "Windows NT","CurrentVersion"])
	val = h.node_get_value(key,"ProductName")
        val = h.value_value(val)
	print
	if "X\x00P" in val[1]:
		print "OS detected: WinXP"
		return "WinXP"
	if "V\x00i\x00s\x00t\x00a" in val[1]:
		print "OS detected: Vista"
		return "Vista"
	if "\x007\x00" in val[1]:
		print "OS detected: Win7"
		return "Win7"
	if "\x008\x00" in val[1]:
		print "OS detected: Win8"
		return "Win8"

###
### Check the path given in arguments are valid before launching anything.
### 
def argument_check(categories, conf_file, hive_dir, system_type):
	try:
		f = open(conf_file)
	except:
		print "Could not find a conf file at the location", conf_file
		return 0
	if not os.path.isdir(hive_dir):
		print "Could not find the directory", hive_dir
		return 0
        f = open(conf_file)
        tree = ElementTree.parse(f)
	if categories:
		for category in categories:
		        node = tree.findall('./' + category)
			if not node:
				print "That category \"", category, "\" is not defined in the XML file"
				return 0
	if system_type is None:
		print "You did not provide the OS name and FRP was not able to identify it automatically"
		return 0
	return 1

###
### If no categories are given in arguments, go over the XML file and add each category to the list.
###
def build_list_category(categories):
	global conf_file	
	if categories: return categories
	categories = []
	f = open(conf_file)
	tree = ElementTree.parse(f)
	root = tree.getroot()
	for child_of_root in root:
		categories.append(child_of_root.tag)
	return categories	

###
### Build the list of children node within the category from the XML file
###
def build_item_list(category):
	global conf_file
	item_list = []
        f = open(conf_file)
        tree = ElementTree.parse(f)
	root = tree.getroot()	
	for item in tree.find(category):
		item_list.append(item.tag)
	return item_list

###
### Return all usernames found in the SAM file as a list of string
###
def build_user_list(hive_dir):
	user_list = []
        try:
                h = hivex.Hivex(hive_dir+"/SAM")
                hive = "/SAM"
        except:
                try:
                        h = hivex.Hivex(hive_dir+"/sam")
                        hive="/sam"
                except:
                        print "Did not find a SAM hive file therefore cannot build the user_list"
        key = get_node(hive_dir+hive, ["SAM", "Domains", "Account","Users", "Names"])
	for element in h.node_children(key):
		user_list.append(h.node_name(element))
	return user_list


class MyOption(Option):
        ACTIONS = Option.ACTIONS + ("extend",)
        STORE_ACTIONS = Option.STORE_ACTIONS + ("extend",)
        TYPED_ACTIONS = Option.TYPED_ACTIONS + ("extend",)
        ALWAYS_TYPED_ACTIONS = Option.ALWAYS_TYPED_ACTIONS + ("extend",)

        def take_action(self, action, dest, opt, value, values, parser):
                if action == "extend":
                        value = value.split(",")
                        values.ensure_value(dest, []).extend(value)
                else:
                        Option.take_action(self, action, dest, opt, value, values, parser)

VERSION='0.2'

def main ():
        PROG = os.path.basename(os.path.splitext(__file__)[0])

        description = """Flexible Registry Parser"""

        ## Command Line Options
        parser = OptionParser(option_class=MyOption, usage='usage: %prog -O OS [OPTIONS]', version='%s %s' % (PROG, VERSION), description=description)
        parser.add_option('-c', '--categories', action="extend", type="string", dest='categories', metavar='CATEGORIES', help='comma separated list of categories')
        parser.add_option('-d', '--directory', type="string", action="store", dest="directory", metavar='PATH', help="Directory where the hive files are located. Default is CWD")
#        parser.add_option('-l', '--list-category', dest="list", metavar='LIST', action="store_true", help="Prints all category lookable in the conf file")
        parser.add_option('-u', '--users', action="extend", type="string", dest='users', metavar='USERS', help='List of user to inspect')
        parser.add_option('-f', '--file-conf', type="string", dest="conf_file", metavar='PATH', help="XML file path. Default is CWD/conf.xml")
        parser.add_option('-O', '--OS', type="choice", dest="OS", choices=["WinXP", "Vista", "Win7", "Win8"], help='Type of OS the hive file has been collected from')
	parser.add_option('-v', '--verbose', type="choice", dest="VERB", choices=["0","1","2","3",], help='define the level of verbosity.\n0 = default\n1 = adds path to the key\n2 = adds \"Last modified time\"\n3  = adds both')


	OPTIONS, args = parser.parse_args()

        if len(sys.argv) == 1:
                parser.parse_args(['--help'])

	global hive_dir
	global VERBOSITY
	global conf_file
	
	system_type = OPTIONS.OS
	categories = OPTIONS.categories
	users = OPTIONS.users
	conf_file = OPTIONS.conf_file
	hive_dir = OPTIONS.directory
	VERBOSITY = OPTIONS.VERB

	if conf_file is None:
		conf_file = os.getcwd()+"/conf.xml"
	if hive_dir is None:
		hive_dir = os.getcwd()
	if VERBOSITY is None:
		VERBOSITY = 0
        if system_type is None:
		system_type = autodetect_os(hive_dir)
	
	if not argument_check(categories, conf_file, hive_dir, system_type):
		print "Exiting...."
		return 0		

	categories = build_list_category(categories)
	user_list = build_user_list(hive_dir)
	
	#print user_list
	
	for category in categories:
        	print
	        print "                         **************************************"
        	print "                                    ", category
	        print "                         **************************************"
        	print
		item_list = build_item_list(category)
		for item in item_list:
			print_information(category, item, system_type)

if __name__ == '__main__':
        main()
